package com.yc.spring.aop;

import com.yc.MyAnnotationConfigApplicationContext;
import com.yc.spring.IocConfig;
import com.yc.spring.bbs.dao.UserDao;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.SourceLocation;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AopApplicationContext extends MyAnnotationConfigApplicationContext {

    //切面对象集合
    public Map<String, Object> aspectMap = new HashMap<>();
    //一个切点表达式可能对应多个增强方法
    //切点映射增强方法集合
    public Map<String, List<Method>> pointcutMap = new HashMap<>();
    //需要动态代理的对象集合
    public Map<String, Object> proxyBeanMap = new HashMap<>();

    //在这段代码中，AopApplicationContext继承自MyAnnotationConfigApplicationContext，
    // 而MyAnnotationConfigApplicationContext有一个带参数的构造函数。
    // 因为子类没有显式定义任何构造函数，所以编译器会试图隐式调用父类的有参构造函数，从而导致编译错误。
    //为了解决这个问题，需要在子类中提供与父类相同的构造函数，通过显式地调用父类的构造函数来确保正确的初始化。
    // 因此，在AopApplicationContext中强制覆盖构造函数是为了确保能够正确地初始化父类的构造函数。
    public AopApplicationContext(Class configClass) throws NoSuchMethodException {
        super(configClass);
        //查找组件中的切面类 => @Aspect 注解
        this.singletonObjects.forEach((id, bean) -> {
            //判断切面类
            if (bean.getClass().getAnnotation(Aspect.class) != null) {
                aspectMap.put(id, bean);
            }
        });

        //统计切点与增强 Pointcut + Advice，封装切面信息对象，保存切面集合中
        for (Object aspectBean : aspectMap.values()) {
            for (Method method : aspectBean.getClass().getDeclaredMethods()) {
                // 只实现前置增强
                Before before = method.getAnnotation(Before.class);
                After after = method.getAnnotation(After.class);
                AfterReturning afterReturning = method.getAnnotation(AfterReturning.class);
                AfterThrowing afterThrowing = method.getAnnotation(AfterThrowing.class);
                if (before != null || afterReturning != null || after != null || afterThrowing != null) {
                    String value = null;
                    if (before != null) {
                        //提取切点表达式
                        value = before.value();
                    } else if (after != null) {
                        value = after.value();
                    } else if (afterThrowing != null) {
                        value = afterThrowing.value();
                    } else if (afterReturning != null) {
                        value = afterReturning.value();
                    }
                    String pointcut = null;
                    if (value.matches("\\w+\\(\\)")) {
                        //@After("myPointcut()")
                        String $1 = value.replaceAll("(\\w)\\(\\)", "$1");
                        //提取切点方法
                        Method pointcutMethod = aspectBean.getClass().getDeclaredMethod($1);
                        //获取切点方法上的切点
                        Pointcut pointcutAnno = pointcutMethod.getAnnotation(Pointcut.class);
                        pointcut = pointcutAnno.value();
                    } else {
                        //@Pointcut("execution(* com.yc.spring.bbs.dao.UserDaoImpl.select*(..))")
                        pointcut = value;
                    }
                    //将切点和增强方法添加到集合
                    List<Method> list = pointcutMap.get(pointcut);
                    //如果list为空。那么是第一次添加
                    if (list == null) {
                        list = new ArrayList<>();
                        pointcutMap.put(pointcut, list);
                    }
                    list.add(method);
                }

            }
        }


        //扫描所有的组件,查看是否包含被切点拦截的方法
        this.singletonObjects.forEach((id, bean) -> {
            for (Method method : bean.getClass().getDeclaredMethods()) {
                a:
                for (String pointcut : pointcutMap.keySet()) {
                    if (isPointcut(pointcut, method)) {
                        //被切到  添加到proxyBean
                        proxyBeanMap.put(id, bean);
                        //只要有一个方法被切到  那么就要代理   即可终止循环
                        break a;
                    }
                }
            }
        });

        //如果组件包含被切点拦截的方法，那么动态生成代理对象替换原有的组件
        proxyBeanMap.forEach((id, bean) -> {
            Class<?>[] interfaces = bean.getClass().getInterfaces();
            if (interfaces == null || interfaces.length == 0) {
                //cglib动态代理
                cglibProxy(id, bean);
            } else {
                //否则  jdk动态代理
                jdpProxy(id, bean);
            }
        });
    }

    //cglib代理
    private Object cglibProxy(String id, Object bean) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(bean.getClass());
        enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
            /**
             * 代理对象
             * 方法
             * 参数
             * 代理方法
             */
                Object ret = methodProxy.invoke(bean, objects);
                System.out.println("后置拦截  " + method);
                return ret;
        });
        return enhancer.create();
    }

    //jdk代理
    private void jdpProxy(String id, Object bean) {
        //动态生成代理对象
        Object proxyBean = Proxy.newProxyInstance(bean.getClass().getClassLoader(),
                bean.getClass().getInterfaces(),
                (proxy, methods, args) -> {
                    //jdk  流式编程
                    try {
                        //前置增强
                        //entrySet()  例如（1，a）=>  封装成一个对象，就变成了一个单链集合
                        pointcutMap
                                //双链集合转的单链集合 Set<Entry<pointcut,list<method>>>
                                .entrySet()
                                //集合转成流
                                .stream()
                                //过滤切点
                                .filter(entry -> isPointcut(entry.getKey(), methods))
                                //将 map里面的list的元素提取出来
                                .flatMap(entry -> entry.getValue().stream())
                                //过滤出前置拦截方法集合
                                .filter(adviceMethod -> adviceMethod.getAnnotation(Before.class) != null)
                                //执行所有的前置增强方法
                                .forEach(beforeMethod -> {
                                    //取出唯一的切面对象
                                    Object myAspect = aspectMap.get("myAspect");
                                    try {
                                        if (beforeMethod.getParameterCount() == 0) {
                                            beforeMethod.invoke(myAspect);
                                        } else if (beforeMethod.getParameterCount() == 1) {
                                            //如果带参数  => 判断参数类型
                                            //简单处理只有一个 JointPoint 参数的情况
                                            System.out.println();
                                            JoinPoint joinPoint = new JoinPoint() {
                                                @Override
                                                public String toShortString() {
                                                    return null;
                                                }

                                                @Override
                                                public String toLongString() {
                                                    return null;
                                                }

                                                @Override
                                                public Object getThis() {
                                                    return null;
                                                }

                                                @Override
                                                public Object getTarget() {
                                                    return null;
                                                }

                                                @Override
                                                public Object[] getArgs() {
                                                    return new Object[0];
                                                }

                                                @Override
                                                public Signature getSignature() {
                                                    return null;
                                                }

                                                @Override
                                                public SourceLocation getSourceLocation() {
                                                    return null;
                                                }

                                                @Override
                                                public String getKind() {
                                                    return null;
                                                }

                                                @Override
                                                public StaticPart getStaticPart() {
                                                    return null;
                                                }
                                            };
                                            beforeMethod.invoke(myAspect, joinPoint);
                                        }
                                    } catch (Exception e) {
                                        throw new RuntimeException("前置增强方法失败！", e);
                                    }
                                });
                        Object ret = methods.invoke(bean, args);
                        //返回增强
                        pointcutMap.entrySet().stream().filter(entry -> isPointcut(entry.getKey(), methods))
                                .flatMap(entry -> entry.getValue().stream())
                                .filter(adviceMethod -> adviceMethod.getAnnotation(AfterReturning.class) != null)
                                .forEach(afterReturningMethod -> {
                                    //取出唯一的切面对象
                                    Object myAspect = aspectMap.get("myAspect");
                                    try {
                                        if (afterReturningMethod.getParameterCount() == 0) {
                                            afterReturningMethod.invoke(myAspect);
                                        } else if (afterReturningMethod.getParameterCount() == 2) {
                                            JoinPoint joinPoint = new JoinPoint() {
                                                @Override
                                                public String toShortString() {
                                                    return null;
                                                }

                                                @Override
                                                public String toLongString() {
                                                    return null;
                                                }

                                                @Override
                                                public Object getThis() {
                                                    return null;
                                                }

                                                @Override
                                                public Object getTarget() {
                                                    return null;
                                                }

                                                @Override
                                                public Object[] getArgs() {
                                                    return new Object[0];
                                                }

                                                @Override
                                                public Signature getSignature() {
                                                    return null;
                                                }

                                                @Override
                                                public SourceLocation getSourceLocation() {
                                                    return null;
                                                }

                                                @Override
                                                public String getKind() {
                                                    return null;
                                                }

                                                @Override
                                                public StaticPart getStaticPart() {
                                                    return null;
                                                }
                                            };
                                            afterReturningMethod.invoke(myAspect, joinPoint, ret);
                                        }
                                    } catch (Exception e) {
                                        throw new RuntimeException("返回拦截方法失败！", e);
                                    }
                                });
                        return ret;
                    } catch (Throwable t) {
                        //异常增强
                        pointcutMap.entrySet().stream().filter(entry -> isPointcut(entry.getKey(), methods))
                                .flatMap(entry -> entry.getValue().stream())
                                .filter(adviceMethod -> adviceMethod.getAnnotation(AfterThrowing.class) != null)
                                .forEach(afterThrowingMethod -> {
                                    //取出唯一的切面对象
                                    Object myAspect = aspectMap.get("myAspect");
                                    try {
                                        if (afterThrowingMethod.getParameterCount() == 0) {
                                            afterThrowingMethod.invoke(myAspect);
                                        } else if (afterThrowingMethod.getParameterCount() == 2) {
                                            JoinPoint joinPoint = new JoinPoint() {
                                                @Override
                                                public String toShortString() {
                                                    return null;
                                                }

                                                @Override
                                                public String toLongString() {
                                                    return null;
                                                }

                                                @Override
                                                public Object getThis() {
                                                    return null;
                                                }

                                                @Override
                                                public Object getTarget() {
                                                    return null;
                                                }

                                                @Override
                                                public Object[] getArgs() {
                                                    return new Object[0];
                                                }

                                                @Override
                                                public Signature getSignature() {
                                                    return null;
                                                }

                                                @Override
                                                public SourceLocation getSourceLocation() {
                                                    return null;
                                                }

                                                @Override
                                                public String getKind() {
                                                    return null;
                                                }

                                                @Override
                                                public StaticPart getStaticPart() {
                                                    return null;
                                                }
                                            };
                                            afterThrowingMethod.invoke(myAspect, joinPoint, t);
                                        }
                                    } catch (Exception e) {
                                        throw new RuntimeException("异常拦截方法失败！", e);
                                    }
                                });
                        throw t;
                    } finally {
                        //后置增强
                        pointcutMap.entrySet().stream().filter(entry -> isPointcut(entry.getKey(), methods))
                                .flatMap(entry -> entry.getValue().stream())
                                .filter(adviceMethod -> adviceMethod.getAnnotation(After.class) != null)
                                .forEach(afterMethod -> {
                                    //取出唯一的切面对象
                                    Object myAspect = aspectMap.get("myAspect");
                                    try {
                                        if (afterMethod.getParameterCount() == 0) {
                                            afterMethod.invoke(myAspect);
                                        } else if (afterMethod.getParameterCount() == 1) {
                                            JoinPoint joinPoint = new JoinPoint() {
                                                @Override
                                                public String toShortString() {
                                                    return null;
                                                }

                                                @Override
                                                public String toLongString() {
                                                    return null;
                                                }

                                                @Override
                                                public Object getThis() {
                                                    return null;
                                                }

                                                @Override
                                                public Object getTarget() {
                                                    return null;
                                                }

                                                @Override
                                                public Object[] getArgs() {
                                                    return new Object[0];
                                                }

                                                @Override
                                                public Signature getSignature() {
                                                    return null;
                                                }

                                                @Override
                                                public SourceLocation getSourceLocation() {
                                                    return null;
                                                }

                                                @Override
                                                public String getKind() {
                                                    return null;
                                                }

                                                @Override
                                                public StaticPart getStaticPart() {
                                                    return null;
                                                }
                                            };
                                            afterMethod.invoke(myAspect, joinPoint);
                                        }
                                    } catch (Exception e) {
                                        throw new RuntimeException("后置增强方法失败！", e);
                                    }
                                });
                    }
                });
        //替换原有的 bean
        singletonObjects.put(id, proxyBean);
    }


    /**
     * 判断该方法是否被切到
     *
     * @param pointcut
     * @param method
     * @return
     */
    public boolean isPointcut(String pointcut, Method method) {
        //只处理 execution 表达式
        //去掉execution()
        pointcut = pointcut.replaceAll("execution\\((.+)\\)", "$1");
        //转成正则表达式
        pointcut = pointcut.replaceAll("\\*|\\.\\.", ".*");
        pointcut = pointcut.replaceAll("\\(", "\\\\(");
        pointcut = pointcut.replaceAll("\\)", "\\\\)");
//        System.out.println("******************   pointcut = " + pointcut);
        String methodString = method.toString();

        //判断是否被切到
        return methodString.matches(pointcut);
    }

    public static void main(String[] args) throws NoSuchMethodException {
        AopApplicationContext cxt = new AopApplicationContext(IocConfig.class);
       /* cxt.pointcutMap.forEach((pointcut,list) ->{
            System.out.println(pointcut);
            System.out.println(list);
        });

        System.out.println();
        System.out.println();
        System.out.println();

        cxt.proxyBeanMap.forEach((id,bean) ->{
            System.out.println(id);
            System.out.println(bean);
        });

        System.out.println("end");*/

        UserDao userDaoImpl = (UserDao) cxt.getBean("userDaoImpl");
        userDaoImpl.insert(null);
    }
}
